package at.tasat.solver;

import java.util.LinkedList;

public final class ThreadManager {
	private LinkedList<ManagedThread> availableThreads = new LinkedList<ManagedThread>();
	private ManagedThread[] threads;

	public ThreadManager(int capacity) {
		assert capacity >= 0;
		threads = new ManagedThread[capacity];
		for (int i = 0; i < threads.length; i++) {
			threads[i] = new ManagedThread();
		}
	}

	public void release() {
		for (int i = 0; i < threads.length; i++) {
			threads[i].stop();
		}
	}

	private void addAvailable(ManagedThread runner) {
		synchronized (availableThreads) {
			availableThreads.addFirst(runner);
		}
	}

	private boolean can(int nThreads) {
		return availableThreads.size() >= nThreads;
	}

	public ManagedThread[] fork(int nThreads) {
		if (can(nThreads)) {
			synchronized (availableThreads) {
				boolean can = can(nThreads);
				if (can) {
					ManagedThread[] managedThreads = new ManagedThread[nThreads];
					for (int i = 0; i < nThreads; i++) {
						managedThreads[i] = availableThreads.removeLast();
					}
					return managedThreads;
				}
				return null;
			}
		}
		return null;
	}

	public class ManagedThread {
		private final Object monitor = new Object();
		private volatile Runnable runnable;
		private Thread thread;

		private ManagedThread() {
			thread = new Thread(runner);
			thread.start();
		}

		private Runnable runner = new Runnable() {
			@Override
			public void run() {
				while (true) {
					synchronized (monitor) {
						Thread.interrupted(); // clear flag
						runnable = null;
						monitor.notifyAll();
						addAvailable(ManagedThread.this);
						while (runnable == null) {
							try {
								monitor.wait();
							} catch (InterruptedException e) {
								thread = null;
								return;
							}
						}
					}
					try {
						runnable.run();
					} catch (Throwable t) {
						System.out.print(t.toString());
					}
				}
			}
		};

		public void start(Runnable runnable) {
			synchronized (monitor) {
				if (thread == null) {
					throw new IllegalAccessError("Thread already terminated");
				}
				if (this.runnable != null) {
					throw new IllegalAccessError("A runnable is already running on this managed thread");
				}
				this.runnable = runnable;
				monitor.notifyAll();
			}
		}

		private void stop() {
			synchronized (monitor) {
				if (thread == null) {
					throw new IllegalAccessError("Thread already terminated");
				}
				while (this.runnable != null) {
					try {
						monitor.wait();
					} catch (InterruptedException e) {
						throw new RuntimeException(e);
					}
				}
				thread.interrupt();
			}
		}

		public void interrupt(Runnable runnable) {
			if (runnable == null) {
				throw new IllegalArgumentException("runnable null");
			}
			synchronized (monitor) {
				if (this.runnable == runnable) {
					thread.interrupt();
				} // in other cases, the thread is already finished
			}
		}

	}

}
